<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title>WebGPU Simple Uniforms</title>
    <style>
        @import url(res/webgpu-lesson.css);
        html, body {
        margin: 0;       /* remove the default margin          */
        height: 100%;    /* make the html,body fill the page   */
        }
        canvas {
        display: block;  /* make the canvas act like a block   */
        width: 100%;     /* make the canvas fill its container */
        height: 100%;
        }
    </style>
</head>
<body>
    <canvas></canvas>
</body>
<script type="module">
    // A random number between [min and max)
    // With 1 argument it will be [0 to min)
    // With no arguments it will be [0 to 1)
    const rand = (min, max) => {
        if (min === undefined) {
            min = 0;
            max = 1;
        } else if (max === undefined) {
            max = min;  
        }
        return min + Math.random() * (max - min);
    };

    async function main() {
        // WebGPU 是异步 API，所以需要使用 async/await 来处理
        const adapter = await navigator.gpu?.requestAdapter();
        const device = await adapter?.requestDevice();
        if (!device) {
            fail('need a browser that supports WebGPU');
            return;
        }

        const canvas = document.querySelector('canvas');    
        const context = canvas.getContext('webgpu');
        // 询问系统首选的画布格式是什么( rgba8unorm 或 bgra8unorm)。
        // 这其实并不重要，重要的是通过查询，可以让用户的系统以最快的速度运行。
        const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
        // 调用 configure 将format传入 webgpu 画布上下文。
        // 将device传入画布，从而将画布与创建的设备关联起来。
        context.configure({
            device,
            format: presentationFormat,
        });

        const wgslCode = `
                struct OurStruct {
                    color: vec4f,
                    scale: vec2f,
                    offset: vec2f,
                };

                @group(0) @binding(0) var<uniform> ourStruct: OurStruct;

                @vertex fn vs(
                    @builtin(vertex_index) vertexIndex : u32
                ) -> @builtin(position) vec4f {
                    let pos = array(
                        vec2f( 0.0,  0.5),  // top center
                        vec2f(-0.5, -0.5),  // bottom left
                        vec2f( 0.5, -0.5)   // bottom right
                    );

                    return vec4f (pos[vertexIndex] * ourStruct.scale + ourStruct.offset, 0.0, 1.0);
                }

                @fragment fn fs() -> @location(0) vec4f {
                    return ourStruct.color;
                }
        `;

        const module = device.createShaderModule({
            label: 'multiple uniforms shaders',
            code: wgslCode,
        });

        const pipeline = device.createRenderPipeline({
            label: 'multiple uniforms pipeline',
            layout: 'auto',
            vertex: {
                module,
            },
            fragment: {
                module,
                targets: [{ format: presentationFormat }],
            },
        });

        // create a buffer for the uniform values
        const uniformBufferSize =
            4 * 4 + // color is 4 32bit floats (4bytes each)
            2 * 4 + // scale is 2 32bit floats (4bytes each)
            2 * 4;  // offset is 2 32bit floats (4bytes each)
        // offsets to the various uniform values in float32 indices
        const kColorOffset = 0;
        const kScaleOffset = 4;
        const kOffsetOffset = 6;

        const kNumObjects = 200;
        const objectInfos = [];

        for (let i = 0; i < kNumObjects; ++i) {
            const uniformBuffer = device.createBuffer({
                label: `uniforms for obj: ${i}`,
                size: uniformBufferSize,
                usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
            });
            
            // create a typedarray to hold the values for the uniforms in JavaScript
            const uniformValues = new Float32Array(uniformBufferSize / 4);

            uniformValues.set([rand(), rand(), rand(), 1], kColorOffset);        // set the color
            // 向画布左侧移动 1/4，向下移动 1/8（剪辑空间从 -1 到 1 的宽度为 2 个单位，因此 0.25 是 2 的 1/8）。
            uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset);      // set the offset

            // 创建一个 bindGroup，将 uniformBuffer 绑定到着色器中的 @group(0) @binding(0)
            // LXQ: 虽然都是@group(0) @bingding(0),但会在pass.setBindGroup()中
            const bindGroup = device.createBindGroup({
                label: `bind group for obj: ${i}`,
                // 0 对应着着色器中的 @group(0)
                layout: pipeline.getBindGroupLayout(0),
                entries: [
                    { binding: 0, resource: { buffer: uniformBuffer }},
                ],
            });

            objectInfos.push({
                scale: rand(0.2, 0.5),
                uniformBuffer,
                uniformValues,
                bindGroup,
            });
        }

        const renderPassDescriptor = {
            label: 'our basic canvas renderPass',
            colorAttachments: [
                {
                    clearValue: [0.3, 0.3, 0.3, 1],
                    // load意思是将纹理的现有内容加载到 GPU 中
                    loadOp: 'clear', // 指定在绘制前将纹理清除为clearValue
                    // storeOp: 'store'表示存储绘制结果。也可儿童discard来丢弃绘制的结果
                    storeOp: 'store', // 
                },
            ],
        };
        
        function render() {
            renderPassDescriptor.colorAttachments[0].view =
                context.getCurrentTexture().createView();

            // LXQ: 把所有要绘制的内容放到一个命令缓冲区中，以提高效率
            // encoder和pass对象只是将命令编码到命令缓冲区中
            const encoder = device.createCommandEncoder({ label: 'our encoder' });
            const pass = encoder.beginRenderPass(renderPassDescriptor);
            pass.setPipeline(pipeline);

            // Set the uniform values in our JavaScript side Float32Array
            const aspect = canvas.width / canvas.height;
            for (const {scale, bindGroup, uniformBuffer, uniformValues} of objectInfos) {
                uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale
                device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
                pass.setBindGroup(0, bindGroup);
                pass.draw(3);
            }
            
            pass.end();

            const commandBuffer = encoder.finish();
            device.queue.submit([commandBuffer]);
        }
    
        // ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。
        // ResizeObserver 避免了通过回调函数调整大小时，通常创建的无限回调循环和循环依赖项。
        const observer = new ResizeObserver(entries => {
            for (const entry of entries) {
                const canvas = entry.target;
                const width = entry.contentBoxSize[0].inlineSize;
                const height = entry.contentBoxSize[0].blockSize;
                canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
                canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));

                render();
            }
        });

        observer.observe(canvas);
    }

    function fail(msg) {
        // eslint-disable-next-line no-alert
        alert(msg);
    }

    main();
</script>


</html>
